package ru.mail.utils
{
		import flash.display.BitmapData;
		import flash.errors.IOError;
		import flash.utils.ByteArray;
		import flash.utils.Endian;
		public class BMPDecoder {
			//___________________________________________________________ const
			private const BITMAP_HEADER_TYPE:String = "BM";
			private const BITMAP_FILE_HEADER_SIZE:int = 14;
			private const BITMAP_CORE_HEADER_SIZE:int = 12;
			private const BITMAP_INFO_HEADER_SIZE:int = 40;
			private const COMP_RGB      :int = 0;
			private const COMP_RLE8     :int = 1;
			private const COMP_RLE4     :int = 2;
			private const COMP_BITFIELDS:int = 3;
			private const BIT1 :int = 1;
			private const BIT4 :int = 4;
			private const BIT8 :int = 8;
			private const BIT16:int = 16;
			private const BIT24:int = 24;
			private const BIT32:int = 32;
			//___________________________________________________________ vars
			private var bytes:ByteArray;
			private var palette:Array;
			private var bd:BitmapData;
			private var _decodeError:String;
			private var nFileSize:uint;
			private var nReserved1:uint;
			private var nReserved2:uint;
			private var nOffbits:uint;
			private var nInfoSize:uint;
			private var nWidth:int;
			private var nHeight:int;
			private var nPlains:uint;
			private var nBitsPerPixel:uint;
			private var nCompression:uint;
			private var nSizeImage:uint;
			private var nXPixPerMeter:int;
			private var nYPixPerMeter:int;
			private var nColorUsed:uint;
			private var nColorImportant:uint;
			private var nRMask:uint;
			private var nGMask:uint;
			private var nBMask:uint;
			private var nRPos:uint;
			private var nGPos:uint;
			private var nBPos:uint;
			private var nRMax:uint;
			private var nGMax:uint;
			private var nBMax:uint;
			/**
			 * Constructor
			 */
			public function BMPDecoder() {
				nRPos = 0;
				nGPos = 0;
				nBPos = 0;
			}
			/**
			 * decoder
			 *
			 * @param BMP file ByteArray
			 */
			public function decode( data:ByteArray ):BitmapData {
				bytes = data;
				bytes.endian = Endian.LITTLE_ENDIAN;
				bytes.position = 0;
				readFileHeader();
				nInfoSize = bytes.readUnsignedInt();
				switch ( nInfoSize ) {
					case BITMAP_CORE_HEADER_SIZE:
						readCoreHeader();
						break;
					case BITMAP_INFO_HEADER_SIZE:
						readInfoHeader();
						break;
					default:
						readExtendedInfoHeader();
						break;
				}
				try{
				bd = new BitmapData( nWidth, nHeight );
				}catch(e:Error){
					
				}
				switch ( nBitsPerPixel ){
					case BIT1:
						readColorPalette();
						decode1BitBMP();
						break;
					case BIT4:
						readColorPalette();
						if ( nCompression == COMP_RLE4 ){
							decode4bitRLE();
						} else {
							decode4BitBMP();
						}
						break;
					case BIT8:
						readColorPalette();
						if ( nCompression == COMP_RLE8 ){
							decode8BitRLE();
						} else {
							decode8BitBMP();
						}
						break;
					case BIT16:
						readBitFields();
						checkColorMask();
						decode16BitBMP();
						break;
					case BIT24:
						decode24BitBMP();
						break;
					case BIT32:
						readBitFields();
						checkColorMask();
						decode32BitBMP();
						break;
					default:
						//throw new VerifyError("invalid bits per pixel : " + nBitsPerPixel );
						trace ("BMPDecoder invalid bits per pixel : " + nBitsPerPixel);
						_decodeError = "BMPDecoder invalid bits per pixel : " + nBitsPerPixel;
						break;
				}
				return bd;
			}
			/**
			 * read BITMAP FILE HEADER
			 */
			private function readFileHeader():void {
				var fileHeader:ByteArray = new ByteArray();
				fileHeader.endian = Endian.LITTLE_ENDIAN;
				try {
					bytes.readBytes( fileHeader, 0, BITMAP_FILE_HEADER_SIZE );
					if ( fileHeader.readUTFBytes( 2 ) != BITMAP_HEADER_TYPE ){
						//throw new VerifyError("invalid bitmap header type");
						trace ("BMPDecoder invalid bitmap header type");
					}
					nFileSize  = fileHeader.readUnsignedInt();
					nReserved1 = fileHeader.readUnsignedShort();
					nReserved2 = fileHeader.readUnsignedShort();
					nOffbits   = fileHeader.readUnsignedInt();
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid file header");
					trace ("BMPDecoder invalid file header");
				}
			}
			/**
			 * read BITMAP CORE HEADER
			 */
			private function readCoreHeader():void {
				var coreHeader:ByteArray = new ByteArray();
				coreHeader.endian = Endian.LITTLE_ENDIAN;
				try {
					bytes.readBytes( coreHeader, 0, BITMAP_CORE_HEADER_SIZE - 4 );
					nWidth  = coreHeader.readShort();
					nHeight = coreHeader.readShort();
					nPlains = coreHeader.readUnsignedShort();
					nBitsPerPixel = coreHeader.readUnsignedShort();
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid core header");
					trace ("BMPDecoder invalid core header");
				}
			}
			/**
			 * read BITMAP INFO HEADER
			 */
			private function readInfoHeader():void {
				var infoHeader:ByteArray = new ByteArray();
				infoHeader.endian = Endian.LITTLE_ENDIAN;
				try {
					bytes.readBytes( infoHeader, 0, BITMAP_INFO_HEADER_SIZE - 4 );
					nWidth  = infoHeader.readInt();
					nHeight = infoHeader.readInt();
					nPlains = infoHeader.readUnsignedShort();
					nBitsPerPixel = infoHeader.readUnsignedShort();
					nCompression = infoHeader.readUnsignedInt();
					nSizeImage = infoHeader.readUnsignedInt();
					nXPixPerMeter = infoHeader.readInt();
					nYPixPerMeter = infoHeader.readInt();
					nColorUsed = infoHeader.readUnsignedInt();
					nColorImportant = infoHeader.readUnsignedInt();
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid info header");
					trace ("BMPDecoder invalid info header");
				}
			}
			/**
			 * read the extend info of BITMAP INFO HEADER
			 */
			private function readExtendedInfoHeader():void {
				var infoHeader:ByteArray = new ByteArray();
				infoHeader.endian = Endian.LITTLE_ENDIAN;
				try {
					bytes.readBytes( infoHeader, 0, nInfoSize - 4 );
					nWidth  = infoHeader.readInt();
					nHeight = infoHeader.readInt();
					nPlains = infoHeader.readUnsignedShort();
					nBitsPerPixel = infoHeader.readUnsignedShort();
					nCompression = infoHeader.readUnsignedInt();
					nSizeImage = infoHeader.readUnsignedInt();
					nXPixPerMeter = infoHeader.readInt();
					nYPixPerMeter = infoHeader.readInt();
					nColorUsed = infoHeader.readUnsignedInt();
					nColorImportant = infoHeader.readUnsignedInt();
					if ( infoHeader.bytesAvailable >= 4 ) nRMask = infoHeader.readUnsignedInt();
					if ( infoHeader.bytesAvailable >= 4 ) nGMask = infoHeader.readUnsignedInt();
					if ( infoHeader.bytesAvailable >= 4 ) nBMask = infoHeader.readUnsignedInt();
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid info header");
					trace ("BMPDecoder invalid info header");
				}
			}
			/**
			 * read bitfields
			 */
			private function readBitFields():void {
				if ( nCompression == COMP_RGB ){
					if ( nBitsPerPixel == BIT16 ){
						// RGB555
						nRMask = 0x00007c00;
						nGMask = 0x000003e0;
						nBMask = 0x0000001f;
					} else {
						//RGB888;
						nRMask = 0x00ff0000;
						nGMask = 0x0000ff00;
						nBMask = 0x000000ff;
					}
				} else if ( ( nCompression == COMP_BITFIELDS ) && ( nInfoSize < 52 ) ){
					try {
						nRMask = bytes.readUnsignedInt();
						nGMask = bytes.readUnsignedInt();
						nBMask = bytes.readUnsignedInt();
					} catch ( e:IOError ) {
						//throw new VerifyError("invalid bit fields");
						trace ("BMPDecoder invalid bit fields");
						_decodeError = "BMPDecoder invalid bit fields";
					}
				}
			}
			/**
			 * read color palette
			 */
			private function readColorPalette():void {
				var i:int;
				var len:int = ( nColorUsed > 0 ) ? nColorUsed : Math.pow( 2, nBitsPerPixel );
				palette = new Array( len );
				for ( i = 0; i < len; ++i ){
					palette[ i ] = bytes.readUnsignedInt();
				}
			}
			/**
			 * decode 1 bit BMP
			 */
			private function decode1BitBMP():void {
				var x:int;
				var y:int;
				var i:int;
				var col:int;
				var buf:ByteArray = new ByteArray();
				var line:int = nWidth / 8;
				if ( line % 4 > 0 ){
					line = ( ( line / 4 | 0 ) + 1 ) * 4;
				}
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						buf.length = 0;
						bytes.readBytes( buf, 0, line );
						for ( x = 0; x < nWidth; x += 8 ){
							col = buf.readUnsignedByte();
							for ( i = 0; i < 8; ++i ){
								bd.setPixel( x + i, y, palette[ col >> ( 7 - i ) & 0x01 ] );
							}
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * decode 4bit RLE
			 */
			private function decode4bitRLE():void {
				var x:int;
				var y:int;
				var i:int;
				var n:int;
				var col:int;
				var data:uint;
				var buf:ByteArray = new ByteArray();
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						buf.length = 0;
						while ( bytes.bytesAvailable > 0 ){
							n = bytes.readUnsignedByte();
							if ( n > 0 ){
								// encode data
								data = bytes.readUnsignedByte();
								for ( i = 0; i < n/2; ++i ){
									buf.writeByte( data );
								}
							} else {
								n = bytes.readUnsignedByte();
								if ( n > 0 ){
									// abs mode
									bytes.readBytes( buf, buf.length, n/2 );
									buf.position += n/2;
									if ( n/2 + 1 >> 1 << 1 != n/2 ){
										bytes.readUnsignedByte();
									}
								} else {
									// EOL
									break;
								}
							}
						}
						buf.position = 0;
						for ( x = 0; x < nWidth; x += 2 ){
							col = buf.readUnsignedByte();
							bd.setPixel( x, y, palette[ col >> 4 ] );
							bd.setPixel( x + 1, y, palette[ col & 0x0f ] );
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * decode 4bit (no Compression)
			 */
			private function decode4BitBMP():void {
				var x:int;
				var y:int;
				var i:int;
				var col:int;
				var buf:ByteArray = new ByteArray();
				var line:int = nWidth / 2;
				if ( line % 4 > 0 ){
					line = ( ( line / 4 | 0 ) + 1 ) * 4;
				}
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						buf.length = 0;
						bytes.readBytes( buf, 0, line );
						for ( x = 0; x < nWidth; x += 2 ){
							col = buf.readUnsignedByte();
							bd.setPixel( x, y, palette[ col >> 4 ] );
							bd.setPixel( x + 1, y, palette[ col & 0x0f ] );
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * decode 8bit( RLE Compression )
			 */
			private function decode8BitRLE():void {
				var x:int;
				var y:int;
				var i:int;
				var n:int;
				var col:int;
				var data:uint;
				var buf:ByteArray = new ByteArray();
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						buf.length = 0;
						while ( bytes.bytesAvailable > 0 ){
							n = bytes.readUnsignedByte();
							if ( n > 0 ){
								// encode data
								data = bytes.readUnsignedByte();
								for ( i = 0; i < n; ++i ){
									buf.writeByte( data );
								}
							} else {
								n = bytes.readUnsignedByte();
								if ( n > 0 ){
									// abs mode data
									bytes.readBytes( buf, buf.length, n );
									buf.position += n;
									if ( n + 1 >> 1 << 1 != n ){
										bytes.readUnsignedByte();
									}
								} else {
									// EOL
									break;
								}
							}
						}
						buf.position = 0;
						for ( x = 0; x < nWidth; ++x ){
							bd.setPixel( x, y, palette[ buf.readUnsignedByte() ] );
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * decode 8bit(no Compression)
			 */
			private function decode8BitBMP():void {
				var x:int;
				var y:int;
				var i:int;
				var col:int;
				var buf:ByteArray = new ByteArray();
				var line:int = nWidth;
				if ( line % 4 > 0 ){
					line = ( ( line / 4 | 0 ) + 1 ) * 4;
				}
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						buf.length = 0;
						bytes.readBytes( buf, 0, line );
						for ( x = 0; x < nWidth; ++x ){
							bd.setPixel( x, y, palette[ buf.readUnsignedByte() ] );
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * decode 16bit
			 */
			private function decode16BitBMP():void {
				var x:int;
				var y:int;
				var col:int;
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						for ( x = 0; x < nWidth; ++x ){
							col = bytes.readUnsignedShort();
							bd.setPixel( x, y, ( ( ( col & nRMask ) >> nRPos )*0xff/nRMax << 16 ) + ( ( ( col & nGMask ) >> nGPos )*0xff/nGMax << 8 ) + ( ( ( col & nBMask ) >> nBPos )*0xff/nBMax << 0 ) );
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * decode 24bit BMP
			 */
			private function decode24BitBMP():void {
				var x:int;
				var y:int;
				var col:int;
				var buf:ByteArray = new ByteArray();
				var line:int = nWidth * 3;
				if ( line % 4 > 0 ){
					line = ( ( line / 4 | 0 ) + 1 ) * 4;
				}
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						buf.length = 0;
						bytes.readBytes( buf, 0, line );
						for ( x = 0; x < nWidth; ++x ){
							bd.setPixel( x, y, buf.readUnsignedByte() + ( buf.readUnsignedByte() << 8 ) + ( buf.readUnsignedByte() << 16 ) );
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * decode 32bit BMP
			 */
			private function decode32BitBMP():void {
				var x:int;
				var y:int;
				var col:int;
				try {
					for ( y = nHeight - 1; y >= 0; --y ){
						for ( x = 0; x < nWidth; ++x ){
							col = bytes.readUnsignedInt();
							bd.setPixel( x, y, ( ( ( col & nRMask ) >> nRPos )*0xff/nRMax << 16 ) + ( ( ( col & nGMask ) >> nGPos )*0xff/nGMax << 8 ) + ( ( ( col & nBMask ) >> nBPos )*0xff/nBMax << 0 ) );
						}
					}
				} catch ( e:IOError ) {
					//throw new VerifyError("invalid image data");
					trace ("BMPDecoder invalid image data");
					_decodeError = "BMPDecoder invalid image data";
				}
			}
			/**
			 * check color mask
			 */
			private function checkColorMask():void {
				if ( ( nRMask & nGMask ) | ( nGMask & nBMask ) | ( nBMask & nRMask ) ){
					//throw new VerifyError("invalid bit fields");
					trace ("BMPDecoder invalid bit fields");
				}
				while ( ( ( nRMask >> nRPos ) & 0x00000001 ) == 0 ){
					nRPos++;
				}
				while ( ( ( nGMask >> nGPos ) & 0x00000001 ) == 0 ){
					nGPos++;
				}
				while ( ( ( nBMask >> nBPos ) & 0x00000001 ) == 0 ){
					nBPos++;
				}
				nRMax = nRMask >> nRPos;
				nGMax = nGMask >> nGPos;
				nBMax = nBMask >> nBPos;
			}
			/**
			 * print information
			 */
			public function traceInfo():void {
				trace("---- FILE HEADER ----");
				trace("nFileSize: " + nFileSize );
				trace("nReserved1: " + nReserved1 );
				trace("nReserved2: " + nReserved2 );
				trace("nOffbits: " + nOffbits );
				trace("---- INFO HEADER ----");
				trace("nWidth: " + nWidth );
				trace("nHeight: " + nHeight );
				trace("nPlains: " + nPlains );
				trace("nBitsPerPixel: " + nBitsPerPixel );
				if ( nInfoSize >= 40 ){
					trace("nCompression: " + nCompression );
					trace("nSizeImage: " + nSizeImage );
					trace("nXPixPerMeter: " + nXPixPerMeter );
					trace("nYPixPerMeter: " + nYPixPerMeter );
					trace("nColorUsed: " + nColorUsed );
					trace("nColorUsed: " + nColorImportant );
				}
				if ( nInfoSize >= 52 ){
					trace("nRMask: " + nRMask.toString( 2 ) );
					trace("nGMask: " + nGMask.toString( 2 ) );
					trace("nBMask: " + nBMask.toString( 2 ) );
				}
			}
			
			/**
			 * report if there were some errors while decoding 
			 * @return 
			 * 
			 */			
			public function get decodeError():String {
				return _decodeError;
			}
		}
	}